package mspbots.tsheets.business.core.times;

import com.fasterxml.jackson.databind.JsonNode;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import mspbots.tsheets.business.core.AbstractToolsUtil;
import mspbots.tsheets.business.core.tsheetuser.TSheetUser;
import mspbots.tsheets.common.ResultMsg;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.BeanUtils;
import org.springframework.data.relational.core.query.Criteria;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.Optional;

/**
 * @author AlexBob
 */
@Log4j2
@Service
@Component
@RequiredArgsConstructor
public class TimeSheetsReceiver extends AbstractToolsUtil {

    @RabbitListener(queues = "tsheets.times.tl")
    public void syncTimesheet(ResultMsg resultMsg) {
        TimeSheet timeSheet = super.objectMapper.convertValue(resultMsg.getEntity(), TimeSheet.class);
        timeSheet.setId(null);
        timeSheet.setSheetId(resultMsg.getId());
        timeSheet.setTenantId(resultMsg.getTenantId());
        timeSheet.setSyncTime(resultMsg.getSyncDate());
        timeSheet.setSheetUserId(resultMsg.getEntity().get("user_id").longValue());
        timeSheet.setCreatedByUserId(resultMsg.getEntity().findValue("created_by_user_id").asLong());
        timeSheet.setNotes(resultMsg.getEntity().findValue("notes").asText());
        timeSheet.setDuration(resultMsg.getEntity().findValue("duration").asLong());
        timeSheet.setLastModified(this.timeCovert(resultMsg.getEntity().findValue("last_modified").textValue()));
        timeSheet.setOneDay(LocalDate.parse(resultMsg.getEntity().findValue("date").textValue()));
        timeSheet.setExtend(resultMsg.getEntity());
        timeSheet.setOnTheClock(resultMsg.getEntity().findValue("on_the_clock").asBoolean());
        timeSheet.setStartTime(!StringUtils.isEmpty(resultMsg.getEntity().findValue("start").asText()) ?
                this.timeCovert(resultMsg.getEntity().findValue("start").textValue()) : null);
        timeSheet.setEndTime(!StringUtils.isEmpty(resultMsg.getEntity().findValue("end").asText()) ?
                this.timeCovert(resultMsg.getEntity().findValue("end").textValue()) : null);
        this.sync(timeSheet);
    }

    private LocalDateTime timeCovert(String textTimeStr) {
        return ZonedDateTime.parse(textTimeStr,
                DateTimeFormatter.ISO_OFFSET_DATE_TIME).withZoneSameInstant(ZoneOffset.UTC).toLocalDateTime();
    }

    public void sync(TimeSheet timeSheet) {

        Optional<Long> userIdOpt = super.databaseClient.execute("SELECT user_id FROM sys_user_mapping " +
                "WHERE tenant_code=:tenantCode AND mapping_user_id=:mappingUserId AND user_type='3' LIMIT 1")
                .bind("tenantCode", timeSheet.getTenantId())
                .bind("mappingUserId", String.valueOf(timeSheet.getSheetUserId()))
                .as(Long.class)
                .fetch().first().blockOptional();

        long userId = userIdOpt.orElse(0L);

        Optional<TSheetUser> userOpt = super.databaseClient.select()
                .from(TSheetUser.class)
                .matching(Criteria.where("tenantCode").is(timeSheet.getTenantId())
                        .and("sheetUserId").is(timeSheet.getSheetUserId()))
                .fetch().first().blockOptional();

        String sheetUserName = null;
        if (userOpt.isPresent()) {
            TSheetUser user = userOpt.get();
            String firstName = StringUtils.isEmpty(user.getFirstName()) ? "" : user.getFirstName();
            String lastName = StringUtils.isEmpty(user.getLastName()) ? "" : user.getLastName();
            sheetUserName = firstName + " " + lastName;
        }

        AttendanceTime inData = AttendanceTime.builder()
                .action("in")
                .sheetId(timeSheet.getSheetId())
                .logTime(timeSheet.getStartTime())
                .date(timeSheet.getOneDay())
                .sheetUserId(timeSheet.getSheetUserId())
                .sheetUserName(sheetUserName)
                .sysUserId(userId)
                .syncTime(LocalDateTime.now())
                .build();
        inData.setTenantCode(timeSheet.getTenantId());

        JsonNode extend = timeSheet.getExtend();
        inData.setLocation(ObjectUtils.isEmpty(extend.get("location")) ? null : extend.get("location").asText());
        inData.setTzStr(ObjectUtils.isEmpty(extend.get("tz_str")) ? null : extend.get("tz_str").asText());
        inData.setType(ObjectUtils.isEmpty(extend.get("type")) ? null : extend.get("type").asText());
        inData.setTz(ObjectUtils.isEmpty(extend.get("tz")) ? null : extend.get("tz").asText());
        inData.setTzTime(setTzTime(inData.getTz()));

        if ("manual".equalsIgnoreCase(inData.getType()) || ObjectUtils.isEmpty(inData.getLogTime())) {
            inData.setAction(null);
            inData.setDuration(ObjectUtils.isEmpty(timeSheet.getDuration()) ? null : timeSheet.getDuration() / 60);
            setManualTsheetsTime(inData);
            return;
        }
        Optional<AttendanceTime> attendanceTimeOpt = super.databaseClient.select()
                .from(AttendanceTime.class)
                .matching(Criteria.where("tenantCode").is(inData.getTenantCode())
                        .and("sheetId").is(inData.getSheetId())
                        .and("action").is("in"))
                .fetch().first().blockOptional();

        if (attendanceTimeOpt.isPresent()) {
            AttendanceTime oldTime =attendanceTimeOpt.get();
            inData.setId(oldTime.getId());
            super.databaseClient.update().table(AttendanceTime.class)
                    .using(inData)
                    .matching(Criteria.where("id").is(inData.getId()))
                    .fetch().rowsUpdated().blockOptional(Duration.ofSeconds(10));
        } else {
            super.databaseClient.insert().into(AttendanceTime.class).using(inData)
                    .fetch().first().blockOptional(Duration.ofSeconds(10));
        }

        attendanceTimeOpt = super.databaseClient.select()
                .from(AttendanceTime.class)
                .matching(Criteria.where("tenantCode").is(timeSheet.getTenantId())
                        .and("sheetId").is(inData.getSheetId())
                        .and("action").is("out"))
                .fetch().first().blockOptional();

        AttendanceTime outDbData = attendanceTimeOpt.orElse(null);

        if (!ObjectUtils.isEmpty(timeSheet.getEndTime())) {
            AttendanceTime outData = AttendanceTime.builder().build();
            BeanUtils.copyProperties(inData, outData);
            outData.setId(null);
            outData.setAction("out");
            outData.setLogTime(timeSheet.getEndTime());
            outData.setDuration(ObjectUtils.isEmpty(timeSheet.getDuration()) ? null : timeSheet.getDuration() / 60);
            if (ObjectUtils.isEmpty(outDbData)) {
                super.databaseClient.insert().into(AttendanceTime.class).using(outData)
                        .fetch().first().blockOptional(Duration.ofSeconds(10));
            } else {
                outData.setId(outDbData.getId());
                super.databaseClient.update().table(AttendanceTime.class)
                        .using(outData)
                        .matching(Criteria.where("id").is(outData.getId()))
                        .fetch().rowsUpdated().blockOptional(Duration.ofSeconds(10));
            }
        } else if (!ObjectUtils.isEmpty(outDbData)) {
            super.databaseClient.delete().from(AttendanceTime.class)
                    .matching(Criteria.where("id").is(outDbData.getId()))
                    .fetch().rowsUpdated().blockOptional(Duration.ofSeconds(10));
        }
    }

    private void setManualTsheetsTime(AttendanceTime times) {
        Optional<AttendanceTime> attendanceTimeOpt = super.databaseClient.select()
                .from(AttendanceTime.class)
                .matching(Criteria.where("tenantCode").is(times.getTenantCode())
                        .and("sheetId").is(times.getSheetId())
                        .and("action").is("in"))
                .fetch().first().blockOptional();
        AttendanceTime inDbData = attendanceTimeOpt.orElse(null);
        if (ObjectUtils.isEmpty(inDbData)) {
            super.databaseClient.insert().into(AttendanceTime.class).using(times)
                    .fetch().rowsUpdated().blockOptional(Duration.ofSeconds(10));
        } else {
            times.setId(inDbData.getId());
            super.databaseClient.update().table(AttendanceTime.class)
                    .using(times)
                    .matching(Criteria.where("id").is(times.getId()))
                    .fetch().rowsUpdated().blockOptional(Duration.ofSeconds(10));
        }
    }

    public String setTzTime(String tzString) {
        Double tz = Double.valueOf(tzString);
        if (tzString == null) {
            return "+00:00";
        }
        String result = "";
        if (!tzString.contains(".")) {
            if (tzString.contains("-")) {
                result += "-";
                if (tz > -10) {
                    result += "0" + tzString.substring(1) + ":00";
                } else {
                    result += tzString.substring(1) + ":00";
                }
            } else {
                result += "+";
                if (tz < 10) {
                    result += "0" + tzString + ":00";
                } else {
                    result += tzString + ":00";
                }
            }
        } else {
            String[] split = tzString.split("\\.");
            String min = split[0];
            String sed = split[1];
            if (min.contains("-")) {
                result += "-";
                if (tz > -10) {
                    result += "0" + min.substring(1) + ":";
                } else {
                    result += min.substring(1) + ":";
                }
            } else {
                result += "+";
                if (tz < 10) {
                    result += "0" + min + ":";
                } else {
                    result += min + ":";
                }
            }
            int i = Integer.valueOf(sed) * 6;
            if (i < 100) {
                result += i;
            } else {
                result += String.valueOf(i).substring(0, 2);
            }
        }
        return result;
    }

}
